<?php


namespace EchoPHP\Database\TuxedoDriver;

use EchoPHP\Database\Exceptions\QueryException;
use EchoPHP\Facades\Log;
use InvalidArgumentException;

class TuxedoStatement extends Handler {

    /**
     * tuxedo服务类型   tuxedo 服务端服务名命名规则   服务名 _ 操作类型   其中操作类型为 SEL UPD BAT  服务名目前有  MYSQL  ORACLE ENGINE
     * 现有服务为  MYSQL_SEL MYSQL_UPD MYSQL_BAT ORACLE_SEL ORACLE_UPD ORACLE_BAT ENGINE_SEL ENGINEOLD_SEL FULL_BAT 后续定义新服务名遵照此规则即可
     * @var string
     */
    protected $server;
    /**
     * 数据库类型  ORACLE | MYSQL
     * @var
     */
    protected $dbType;

    /**
     * 数据库id
     * @var int
     */
    protected $dbId = 1;
    /**
     * 执行sql
     * @var string
     */
    protected $sql = '';
    protected $originSql = '';
    /**
     * 绑定变量值
     * @var array
     */
    protected $bindVar = [];
    protected $originBindVar = [];

    /**
     * sql类型  查询类或者更新类
     * @var string
     */
    const HANDLE_TYPE_SEL = 'SEL';
    const HANDLE_TYPE_UPD = 'UPD';
    protected $sqlType;

    /**
     * 绑定变量类型
     */
    const PARAM_BOOL = 1;
    const PARAM_NULL = 2;
    const PARAM_INT = 3;
    const PARAM_STR = 4;
    const PARAM_FUNC = 5;


    public function __construct($server, $dbType, $dbId, $sql, array $options = []) {
        parent::__construct();

        $this->setServer($server);
        $this->setDbType($dbType);
        $this->setDbId($dbId);
        $this->prepareSql($sql);

        foreach ($options as $attributeName => $attributeValue) {
            $this->setAttribute($attributeName, $attributeValue);
        }
    }

    /**
     * 单条语句执行
     * @return mixed
     */
    public function execute() {
        try {
            $serverName = $this->server . '_' . $this->sqlType;
            $this->setServerName($serverName);
            $sendBufferParams = $this->mergeSendBuffer(false);
            $this->setSendBuffer('');
            $this->pushSendBuffer($sendBufferParams);

            return $this->callTuxedo();
        } catch (\Exception $e) {
            $sqlContext = [
                'query' => $this->sql,
                'bindings' => $this->originBindVar,
                'message' => $e->getMessage(),
            ];
            if (method_exists($e, 'getServerName')) {
                $sqlContext['servername'] = $e->getServerName();
                $sqlContext['sendbuffer'] = $e->getSendBuffer();
                $sqlContext['receivebuffer'] = $e->getReceiveBuffer();
            }
            Log::error('SQL执行出现错误', $sqlContext, 'errorsql');
            throw new QueryException($this->originSql, $this->originBindVar, $e);
        }
    }

    /**
     * 设置tuxedo服务类型
     * @param $server
     */
    protected function setServer($server) {
        $this->server = strtoupper($server);
    }

    /**
     * 设置数据库类型
     * @param $dbType
     */
    protected function setDbType($dbType) {
        $this->dbType = strtoupper($dbType);
    }

    /**
     * 设置statement的dbid
     * @param $dbId
     */
    protected function setDbId($dbId) {
        $this->dbId = intval($dbId) >= 1 ? intval($dbId) : 1;
    }

    /**
     * 获取statement的dbid
     * @return int
     */
    public function getDbId() {
        return $this->dbId;
    }

    public function getSql() {
        return $this->originSql;
    }

    public function getVar() {
        return $this->originBindVar;
    }

    /**
     * 获取tuxedo服务类型
     * @return mixed
     */
    public function getServer() {
        return $this->server;
    }

    /**
     * 获取tuxedo服务操作的数据库类型
     * @return mixed
     */
    public function getDbType() {
        return $this->dbType;
    }

    /**
     * 获取sql的操作类型
     * @return mixed
     */
    public function getSqlType() {
        return $this->sqlType;
    }

    /**
     * sql语句预处理
     * @param string $sql
     */
    protected function prepareSql($sql) {
        $this->originSql = $sql;
        //整理sql的绑定标识
        if ('ORACLE' == $this->dbType) {
            //oracle语句使用 ;x1 ;x2 这样的绑定标识
            $segments = explode('?', $sql);
            $sql = array_shift($segments);
            foreach ($segments as $key => $segment) {
                $sql .= ':x' . ($key + 1) . $segment;
            }
        }
        $this->sql = trim($sql);

        if (0 == strncasecmp($this->sql, 'DELETE', 6)
            || 0 == strncasecmp($this->sql, 'INSERT', 6)
            || 0 == strncasecmp($this->sql, 'UPDATE', 6)
            || 0 == strncasecmp($this->sql, 'REPLACE', 6)
        ) {
            $this->sqlType = self::HANDLE_TYPE_UPD;
        } else {
            $this->sqlType = self::HANDLE_TYPE_SEL;
        }
    }

    /**
     * 绑定数组
     * @param $values
     */
    public function bindValues($values) {
        $values = array_values($values);
        foreach ($values as $value) {
            if (is_null($value)) {
                $this->bindValue($value, self::PARAM_NULL);
            } elseif (is_bool($value)) {
                $this->bindValue($value, self::PARAM_BOOL);
            } elseif (is_int($value)) {
                $this->bindValue($value, self::PARAM_INT);
            } else {
                $this->bindValue($value, self::PARAM_STR);
            }
        }
    }

    /**
     * 绑定变量
     * @param $value            变量值
     * @param int $valueType 变量数据类型
     */
    public function bindValue($value, $valueType = self::PARAM_STR) {
        array_push($this->originBindVar, $value);
        array_push($this->bindVar, [
            $value,
            $valueType
        ]);
    }

    /**
     * 给语句设置一个属性
     * @param $attribute
     * @param $value
     */
    public function setAttribute($attribute, $value) {
        switch ($attribute) {
            case self::ATTR_CASE:
                $this->setFieldCase($value);
                break;
            case self::ATTR_DEFAULT_FETCH_MODE:
                $this->setFetchMode($value);
                break;
        }
    }

    /**
     * 获取受影响记录数
     * @return int
     */
    public function getAffectedRowCount() {
        return $this->affectedRows;
    }

    /**
     * 获取最后插入id
     * @return string
     */
    public function getLastInsertId() {
        return $this->lastInsertId;
    }

    /**
     * 获取结果集记录数
     * @return int
     */
    public function getRowCount() {
        return $this->rowCount;
    }

    /**
     * 从返回集合汇总获取一条记录
     * @return array|bool|\stdClass
     */
    public function fetch() {
        if ($this->fetchMode === self::FETCH_OBJ) {
            $row = new \stdClass();
        } elseif ($this->fetchMode === self::FETCH_ASSOC) {
            $row = [];
        } else {
            $row = [];
        }

        foreach ($this->fieldNames as $key => $fieldName) {
            if (false === ($content = $this->parseReceiveBuffer())) {
                $this->errorCode = -99;
                $this->errorInfo = '解析第' . ($key + 1) . '个字段名出错';
                return false;
            }
            if ($this->fetchMode === self::FETCH_OBJ) {
                $row->$fieldName = $content;
            } elseif ($this->fetchMode === self::FETCH_ASSOC) {
                $row[$fieldName] = $content;
            } else {
                $row[$key] = $content;
            }
        }
        return $row;
    }

    /**
     * 获取所有数据集
     * @return array
     */
    public function fetchAll() {
        $rows = [];
        while (false !== ($row = $this->fetch())) {
            $rows[] = $row;
        }
        return $rows;
    }

    /**
     * 拼装语句的sendBuffer
     * @param bool $mergeForBatch 是否是为事务操作拼接sendbuf， 如果是事务操作，那么不拼接dbid
     * @return array
     */
    public function mergeSendBuffer($mergeForBatch = false) {
        $sendBufferParams = [];
        // sql相关sendbuf   DBID -> SQL [-> 返回列名称] -> varcount -> var1 -> var2 ... ->
        if (!$mergeForBatch) {
            array_push($sendBufferParams, $this->dbId);
        }
        array_push($sendBufferParams, $this->sql);
        if ($this->sqlType == self::HANDLE_TYPE_SEL) {
            //查询语句比其他语句多一个是否返回字段名的标识
            array_push($sendBufferParams, 1);
        }
        ksort($this->bindVar);
        array_push($sendBufferParams, count($this->bindVar));

        foreach ($this->bindVar as $item) {
            switch ($item[1]) {
                case self::PARAM_STR:
                case self::PARAM_FUNC:
                    //理论上func名不应该作为绑定变量，所以只能当成字符串来处理
                case self::PARAM_INT:
                    array_push($sendBufferParams, $item[0]);
                    break;
                case self::PARAM_BOOL:
                    array_push($sendBufferParams, ($item[0] ? 1 : 0));
                    break;
                case self::PARAM_NULL:
                    array_push($sendBufferParams, '');
                    break;
            }
        }
        return $sendBufferParams;
    }

}